
package Commons;

import java.io.FileOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;

/**Questa classe implementa un Thread
 * che gestisce la ricezione affidabile del singolo file da parte di un client
 * oggetto della classe ClientTrasferimentoFileUDP
 *
 * @author Team5
 */
public class ThreadRicezioneFileUDP implements Runnable{

    
    private DatagramSocket fileServerSocket;
    
    private String stringaSalvataggioFile;
    private int numeroDiPacchetti;
    private int dimensionePadding;

    private int bufferDatagramSize;
    private byte buffDatagram[]=null;

    private int bufferFileSize ;
    private byte buffFile [] =null;

    private int bufferContatoreSize;
    private byte buffContatore [] =null;

    // Gli attributi non utilizzati sono stati comunque definiti per una futura
    // customizzazione da parte degli utenti che intenderà utilizzare il Server
    // ed il Client del TrasferimentoFileUDP con buffer di dimensioni differenti
    // rispetto a quelle di default. Questa scelta permetterà quindi una creazione
    // personalizzata del Datagram UDP.
    
  /**Costruttore con parametri in ingresso..
     *
     * @param nomeFile contiene il path relativo insieme al nome del file
     * @param numeroDiPacchetti il numero dei Datagrams attesi in ricezione
     * @param dimPadding il padding adoperato per la creazione dell'ultimo Datagram
     */
    public ThreadRicezioneFileUDP(String nomeFile, int numeroPacchetti, int dimPadding) throws SocketException {
        this.stringaSalvataggioFile = nomeFile;
        this.numeroDiPacchetti = numeroPacchetti;
        this.dimensionePadding = dimPadding;

        bufferDatagramSize = 1028;
        bufferFileSize = 1024;
        bufferContatoreSize = 4;

        buffDatagram=new byte[bufferDatagramSize];
        buffFile  = new byte[bufferFileSize];
        buffContatore  = new byte[bufferContatoreSize];

        fileServerSocket = new DatagramSocket();
        


    }



/**Questo metodo serve ad ottene la porta su cui la Socket è in attesa della 
 * ricezione vera e propria del file.
 *
 * @return il numero di porta restituito
 */
    public int getPortaRicezioneFile()
    {
        return fileServerSocket.getLocalPort();
    }

/**
     * run() del Thread
     */

    public void run() {

      try
      {
            System.out.println("Thread di ricezione file in ascolto sulla porta " + fileServerSocket.getLocalPort());
            //Impostazione di un timeout per la ricezione dei pacchetti
            fileServerSocket.setSoTimeout(500);

            //Creazione dello stream per la scrittura del file
            FileOutputStream out = new FileOutputStream(stringaSalvataggioFile);

            //Impostazione del formato dei pacchetti da ricevere
            DatagramPacket receivePacket = new DatagramPacket(buffDatagram, bufferDatagramSize);

            //Creazione di due variabili per la gestione dell'ordinamento del contenuto dei vari pacchetti
            byte [] numeroPacchetto = new byte [bufferContatoreSize];
            byte [][] payloadPacchetti = new byte [numeroDiPacchetti][];


            System.out.println("SERVER: NumeroDiPacchetti: " + numeroDiPacchetti);

            //Ciclo di ricezione dei pacchetti del file:
            //Avvio ascolto sul socket;
            //ricezione del datagram e prelievo del numero del pacchetto
            //e salvataggio del payload nel vettore
            //nella posizione corrispondente al numero di pacchetto letto.
            int portaUsataDalClient=0;
            InetAddress addressDelClient;
            for (int i=0; i<numeroDiPacchetti; i++)
                {
                try
                    {
                    fileServerSocket.receive(receivePacket);
                    }
                //Se viene sollevato il timeout prima della ricezione di tutti i pacchetti,
                //si suppone la perdita;
                //pertanto si esce dal ciclo e si dà inizio alla ricostruzione del file
                //chiedendo la ritrasmissione dei pacchetti mancanti
                catch (SocketTimeoutException timeout)
                    {
                    System.out.println("SERVER: Raggiunto timeout durante la ricezione del file");
                    fileServerSocket.close();
                    break;
                    }
                buffDatagram=receivePacket.getData();
                System.arraycopy(buffDatagram, 0, numeroPacchetto, 0, bufferContatoreSize);
                int posizioneDati = Integer.parseInt((new String(numeroPacchetto)).trim());
                payloadPacchetti[posizioneDati] = new byte[bufferFileSize];
                System.arraycopy(buffDatagram, bufferContatoreSize, payloadPacchetti[posizioneDati], 0, bufferFileSize);


                System.out.println("SERVER: ricevuto pacchetto: "+posizioneDati);


                }
                portaUsataDalClient=receivePacket.getPort();
                addressDelClient=receivePacket.getAddress();
            //Ciclo per la ricostruzione del file a partire dai pacchetti ricevuti:
            //ricostruisco il file mediante la scrittura ordinata dei vari payload;
            //se ne manca qualcuno mi interrompo,
            //ne richiedo il trasferimento,
            //e riprendendo l'operazione dopo averlo ricevuto

             for(int i=0;i<payloadPacchetti.length;i++)
             {
                 if (payloadPacchetti[i] == null)
                 {
                     System.out.println("SERVER:Richiesta pacchetto mancante. Numero " + i);
                     byte [] pacchettoMancante = new byte[1024];
                     pacchettoMancante = (new Integer(i)).toString().getBytes();
                     DatagramPacket richiestaRitrasmissionePacchetto = new DatagramPacket(pacchettoMancante, pacchettoMancante.length, addressDelClient, portaUsataDalClient);
                     fileServerSocket.send(richiestaRitrasmissionePacchetto);
                     try
                     {
                        fileServerSocket.receive(receivePacket);
                        buffDatagram=receivePacket.getData();
                        System.arraycopy(buffDatagram, 0, numeroPacchetto, 0, bufferContatoreSize);
                        payloadPacchetti[i] = new byte[bufferFileSize];
                        System.arraycopy(buffDatagram, bufferContatoreSize, payloadPacchetti[i], 0, bufferFileSize);
                     }
                     catch (SocketTimeoutException timeout)
                     {
                        System.out.println("SERVER: Raggiunto timeout durante la ricezione del pacchetto mancante");
                        i--;
                        fileServerSocket.close();
                     }


                 }
                }

            // Creazione fisica del file sul filesystem del server

            for(int i=0;i<payloadPacchetti.length-1;i++)
            {
                    out.write(payloadPacchetti[i]);
                    out.flush();
             }
                
            // L'ultimo Datagram è stato creato dal Client mediante l'aggiunta
            // di un padding di dimensione variabile per rispettare la grandezza
            // del buffer del file.
            // Per la corretta ricostruzione del file si elimina il padding prima
            // della scrittura.

            byte [] payloadSenzaPadding = new byte [bufferFileSize-dimensionePadding];
            System.arraycopy(payloadPacchetti[payloadPacchetti.length-1],0,payloadSenzaPadding,0,bufferFileSize-dimensionePadding);
            out.write(payloadSenzaPadding);
            out.flush();
            out.close();

            // Per la conclusione del trasferimento, si invia al Client un Datagram
            // di notifica.

            byte [] totalePacchetti = (new Integer(payloadPacchetti.length)).toString().getBytes();
            DatagramPacket confermaRicezioneTotalePacchetti = new DatagramPacket(totalePacchetti, totalePacchetti.length, addressDelClient, portaUsataDalClient);
            System.out.println("SERVER: Invio conferma ricezione totale dei pacchetti su portaClient:"+ portaUsataDalClient);
            fileServerSocket.send(confermaRicezioneTotalePacchetti);
            System.out.println("SERVER: Conferma ricezione totale dei pacchetti inviata");
            fileServerSocket.close();
            
           
    

     
      }
    catch(Exception eee)
            {
            fileServerSocket.close();//27/1
            eee.printStackTrace();
    }








     
    }

}
